
#include "CmdLine.h"

#ifdef USE_SPDLOG
#include "spdlog/spdlog.h"
#include "spdlog/logger.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#endif // USE_SPDLOG

#include "Application.h"
#include "Paint.h"
#include "FileSystem.h"

#include "../luaapp/LuaApp.h"

namespace LuaGui {
	Application* Application::s_application = nullptr;
	std::string Application::LuaGuiVirtaulPath = "__luagui__";                // �ļ�ϵͳ��Ŀ¼
	std::string Application::LuaGuiAssetPath = "__luagui__";                  // ��׿assetר��Ŀ¼
	std::string Application::LuaGuiScriptPath = "script";                     // �ű�Ŀ¼��
	std::string Application::LuaGuiPackagePath = "package";                   // ��Ŀ¼��
	std::string Application::LuaGuiFontPath = "DroidSansFallback.ttf";        // �ű�Ŀ¼��
	std::string Application::LuaGuiLogPath = "log.txt";                       // �ű�Ŀ¼��
	
	Application::Application()
	{
		m_app_name = "LuaGui";
		m_window_title = m_app_name;

		m_timer_interval = 20; // ms
		m_exited = false;
		m_cmdline = new cmdline::parser();

		m_screen_width = 0;
		m_screen_height = 0;
		m_window_width = 0;
		m_window_height = 0;
		m_window_scale = 1;

		m_storage_directory = GetReadWriteDirectory();
		m_virtual_directory = m_storage_directory / LuaGuiVirtaulPath;
		m_package_directory = LuaGuiPackagePath;
		m_asset_directory = LuaGuiAssetPath;
		m_script_directory = LuaGuiScriptPath;
		m_font_filepath = LuaGuiFontPath;
		m_log_filepath = LuaGuiLogPath;
		m_dev_env = false;
		m_env = "online";
	}

	Application::~Application()
	{
		delete m_cmdline;
	}

	void Application::InitCmdLine()
	{
		m_storage_directory = GetReadWriteDirectory();
		m_virtual_directory = m_storage_directory / LuaGuiVirtaulPath;
		m_package_directory = LuaGuiPackagePath;
#ifdef LUAGUI_ASSET_DIRECTORY
		m_asset_directory = fs::path(LUAGUI_ASSET_DIRECTORY).lexically_normal();
#endif
#ifdef LUAGUI_SCRIPT_DIRECTORY
		m_script_directory = fs::path(LUAGUI_SCRIPT_DIRECTORY).lexically_normal();
#endif
#ifdef LUAGUI_FONT_FILEPATH
		m_font_filepath = fs::path(LUAGUI_FONT_FILEPATH).lexically_normal();
#endif
#ifdef LUAGUI_LOG_FILEPATH
		m_log_filepath = fs::path(LUAGUI_LOG_FILEPATH).lexically_normal();
#endif
#if defined(DEBUG) || defined(_DEBUG)
		m_dev_env = true;
#endif // _DEBUG

		m_cmdline->add<std::string>("storage_directory", (char)0, "storage directory", false, m_storage_directory.u8string());
		m_cmdline->add<std::string>("package_directory", (char)0, "package directory", false, m_package_directory.u8string());
		m_cmdline->add<std::string>("asset_directory", (char)0, "asset directory", false, m_asset_directory.u8string());
		m_cmdline->add<std::string>("script_directory", (char)0, "script directory", false, m_script_directory.u8string());
		m_cmdline->add<std::string>("bootstrapper", (char)0, "lua startup script", false, m_bootstrapper.u8string());
		m_cmdline->add<std::string>("logfile", (char)0, "log filename", false, m_log_filepath.u8string());
		m_cmdline->add<std::string>("fontfile", (char)0, "font filename", false, m_font_filepath.u8string());
		m_cmdline->add<std::string>("env", (char)0, "environment", false, m_env);
	}

	void Application::ParseCmdLine()
	{
		if (m_cmdline->exist("storage_directory")) m_storage_directory = m_cmdline->get<std::string>("storage_directory");
		if (m_cmdline->exist("package_directory")) m_package_directory = m_cmdline->get<std::string>("package_directory");
		if (m_cmdline->exist("asset_directory")) m_asset_directory = m_cmdline->get<std::string>("asset_directory");
		if (m_cmdline->exist("script_directory")) m_script_directory = m_cmdline->get<std::string>("script_directory");
		if (m_cmdline->exist("logfile")) m_log_filepath = m_cmdline->get<std::string>("logfile");
		if (m_cmdline->exist("fontfile")) m_font_filepath = m_cmdline->get<std::string>("fontfile");
		if (m_cmdline->exist("bootstrapper")) m_bootstrapper = m_cmdline->get<std::string>("bootstrapper");
		if (m_cmdline->exist("env")) m_env = m_cmdline->get<std::string>("env");

		if (!m_storage_directory.is_absolute()) m_storage_directory = GetReadWriteDirectory() / m_storage_directory;
		if (!m_package_directory.is_absolute()) m_package_directory = m_storage_directory / m_package_directory;
		//if (!m_asset_directory.is_absolute()) m_asset_directory = m_storage_directory / m_asset_directory;
		if (!m_script_directory.is_absolute()) {
#ifdef AUTO_LOAD_SCRIPT_DATA
			m_script_directory = m_virtual_directory / m_script_directory;
#elifndef LUAGUI_SCRIPT_DIRECTORY
			m_script_directory = m_asset_directory / m_script_directory;
#endif // AUTO_LOAD_SCRIPT_DATA
		}
		
		//LuaGui::FileSystem::CreateDirectory(m_storage_directory);
		//LuaGui::FileSystem::CreateDirectory(m_package_directory);
		//LuaGui::FileSystem::CreateDirectory(m_asset_directory);
		//LuaGui::FileSystem::CreateDirectory(m_script_directory);

		if (!m_font_filepath.empty() && !m_font_filepath.is_absolute()) {
#ifdef AUTO_LOAD_FONT_DATA
			m_font_filepath = m_virtual_directory / m_font_filepath;
#elifndef LUAGUI_FONT_FILEPATH
			m_font_filepath = m_asset_directory / m_font_filepath;
#endif // AUTO_LOAD_FONT_DATA
		}

		if (!m_log_filepath.empty() && !m_log_filepath.is_absolute()) m_log_filepath = m_storage_directory / m_log_filepath;

		Info("\n\nusage: " + m_cmdline->usage());
		Info("Storage Directory: " + m_storage_directory.u8string());
		Info("Virtual Directory: " + m_virtual_directory.u8string());
		Info("Package Directory: " + m_package_directory.u8string());
		Info("Asset Directory: " + m_asset_directory.u8string());
		Info("Script Directory: " + m_script_directory.u8string());
		Info("Font File Path: " + m_font_filepath.u8string());
		Info("Log File Path: " + m_log_filepath.u8string());
		Info("Bootstrapper: " + m_bootstrapper.u8string());
	}

	// Ĭ��Ϊ��ǰ����Ŀ¼
	std::string Application::GetReadWriteDirectory() {
		return fs::current_path().u8string();
	}

	void Application::OnInit() {
		m_exited = false;
	}

	void Application::OnExit() {
		m_exited = true;
	}

	void Application::Init(int argc, char* argv[]) {
		InitCmdLine();
		m_cmdline->parse(argc, argv);

		ParseCmdLine();
		LuaScriptApplication* lua_script_app = (LuaScriptApplication*)GetScriptApplicationInstance();
		LuaApp::Register(lua_script_app->GetLuaState());
		
		luaL_requiref(lua_script_app->GetLuaState(), "md5_core", luaopen_md5_core, 0);
		luaL_requiref(lua_script_app->GetLuaState(), "des56", luaopen_des56, 0);

#ifdef USE_LSQLITE
		luaL_requiref(lua_script_app->GetLuaState(), "lsqlite3", luaopen_lsqlite3, 0);
#endif // USE_LSQLITE
#ifdef USE_LUA_CJSON
		luaL_requiref(lua_script_app->GetLuaState(), "cjson", luaopen_cjson, 0);
#endif // USE_LUA_CJSON
#ifdef USE_LUV
		luaL_requiref(lua_script_app->GetLuaState(), "luv", luaopen_luv, 0);
#endif // USE_LUV
#ifdef USE_LUA_CURL
		luaL_requiref(lua_script_app->GetLuaState(), "lcurl", luaopen_lcurl, 0);
		luaL_requiref(lua_script_app->GetLuaState(), "lcurl_safe", luaopen_lcurl_safe, 0);
#endif
#ifdef USE_LUA_EXPAT
		luaL_requiref(lua_script_app->GetLuaState(), "lxp", luaopen_lxp, 0);
#endif
		OnInit();

#ifdef AUTO_LOAD_SCRIPT
		StartScript();
#endif 
	}

	// thread
	std::string Application::GetCurrentThreadStringId() {
		std::ostringstream oss;
		oss << std::this_thread::get_id();
		return oss.str();
	}

	std::string Application::GetMainThreadStringId() {
		std::ostringstream oss;
		if (m_main_thread_local) oss << m_main_thread_local->GetTid();
		else oss << 0;
		return oss.str();
	}

	void Application::OnLoopOnce()
	{
		OnTimer();
		Render();
	}

	void Application::Loop()
	{
		while (!m_exited) {
			std::this_thread::sleep_for(std::chrono::milliseconds(m_timer_interval));
			OnLoopOnce();
		}
	}

	void Application::OnTimer()
	{
		GetScriptApplicationInstance()->OnTimer();
	}

	// mouse
	void Application::OnMouse(std::shared_ptr<VirtualMouseEvent> e)
	{
		static unsigned long long s_last_move_timestmap = 0;
		static int last_mouse_move_x = 0;
		static int last_mouse_move_y = 0;

		int mouse_button = 0;
		if (e->IsMouseLeftButton()) mouse_button = 1;
		else if (e->IsMouseRightButton()) mouse_button = 2;
		else if (e->IsMouseMiddleButton()) mouse_button = 3;
		else mouse_button = 0;

		int mouse_x = e->GetMouseX();
		int mouse_y = e->GetMouseY();
		int x = int(mouse_x / m_window_scale);
		int y = int(mouse_y / m_window_scale);

		if (e->GetType() == EVirtualEvent::MOUSE_MOVE) {
			unsigned long long current_timestamp = GetTimeStamp();
			if ((current_timestamp - s_last_move_timestmap) < 50) return;
			last_mouse_move_x = mouse_x;
			last_mouse_move_y = mouse_y;
			s_last_move_timestmap = current_timestamp;
			OnMouseMove(x, y);
		}
		else if (e->GetType() == EVirtualEvent::MOUSE_DOWN) {
			last_mouse_move_x = mouse_x;
			last_mouse_move_y = mouse_y;
			OnMouseDown(x, y, mouse_button);
		}
		else if (e->GetType() == EVirtualEvent::MOUSE_UP) {
			if (last_mouse_move_x != mouse_x || last_mouse_move_y != mouse_y) OnMouseMove(x, y);
			last_mouse_move_x = mouse_x;
			last_mouse_move_y = mouse_y;
			OnMouseUp(x, y, mouse_button);
		}
		else if (e->GetType() == EVirtualEvent::MOUSE_WHEEL) {
			OnMouseWheel(x, y);
		}
		else {
		}
	}

	void Application::OnMouseDown(int mouse_x, int mouse_y, int mouse_button)
	{
	}
	void Application::OnMouseMove(int mouse_x, int mouse_y)
	{
	}
	void Application::OnMouseUp(int mouse_x, int mouse_y, int mouse_button)
	{
	}
	void Application::OnMouseWheel(int mouse_x, int mouse_y, int mouse_wheel_delta)
	{
	}
	// key
	void Application::OnKey(std::shared_ptr<VirtualKeyEvent> e)
	{
		if (e->GetType() == EVirtualEvent::KEY_DOWN)
		{
			OnKeyDown((int)e->GetVirtualKey());
		}
		else
		{
			OnKeyUp((int)e->GetVirtualKey());
		}
	}
	void Application::OnKeyDown(unsigned int virtual_key)
	{
	}
	void Application::OnKeyUp(unsigned int virtual_key)
	{
	}
	void Application::OnChar(std::shared_ptr<VirtualCharEvent> e)
	{
	}

	// size 
	void Application::SetWindowWidth(int width)
	{
		SetWindowWidthHeight(width, m_window_height);
	}
	void Application::SetWindowHeight(int height)
	{
		SetWindowWidthHeight(m_window_width, height);
	}
	void Application::SetWindowWidthHeight(int width, int height)
	{
		if (m_window_width == width && m_window_height == height) return;
		m_window_width = width;
		m_window_height = height;
		OnWindowSize();
	}
	void Application::OnWindowSize()
	{
	}

	void Application::SetScreenWidth(int width)
	{
		SetScreenWidthHeight(width, m_screen_height);
	}
	void Application::SetScreenHeight(int height)
	{
		SetScreenWidthHeight(m_screen_width, height);
	}
	void Application::SetScreenWidthHeight(int width, int height)
	{
		if (m_screen_width == width && m_screen_height == height) return;
		m_screen_width = width;
		m_screen_height = height;
		OnScreenSize();
	}
	void Application::OnScreenSize()
	{
	}

	// asset 
	bool Application::LoadAsset(std::string filepath, std::vector<int8_t>& buff)
	{
		return false;
	}

	std::string Application::GetAssetTextByFilePath(std::string filepath) {
		std::vector<int8_t> buff;
		if (LoadAsset(filepath, buff)) return std::string(buff.begin(), buff.end());
		return "";
	}

	// script
	std::string Application::GetScriptTextByFilePath(std::string filepath)
	{
		return FileSystem::GetFileText(filepath);
	}

	std::string Application::GetTextByFilePath(std::string filepath) {
		return FileSystem::GetFileText(filepath);
	}

	void Application::Debug(std::string msg) {
		GetConsoleLogger()->Debug(msg);
	}
	void Application::Info(std::string msg) {
		GetConsoleLogger()->Info(msg);
	}
	void Application::Warn(std::string msg) {
		GetConsoleLogger()->Warn(msg);
	}
	void Application::Error(std::string msg) {
		GetConsoleLogger()->Error(msg);
	}

	void Application::StartScript() {
		static bool bStart = false;
		if (bStart) return;
		bStart = true;

		LuaScriptApplication* lua_script_app = (LuaScriptApplication*)GetScriptApplicationInstance();
		fs::path script_init(L"init.lua");
		std::string filename = (GetScriptDirectory() / script_init).lexically_normal().u8string();
		std::string text = GetScriptTextByFilePath(filename.c_str());
		lua_script_app->DoString(text.c_str());
	}
}
